using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using SharpObjects.Interfaces;
using SharpObjects.PathMapping;

namespace SharpObjects.Instantiation
{
    public  class InstanceFactory : IInstanceFactory
    {
        private const string dllExtension = ".dll";
        private const string exeExtension = ".exe";
        private const string aspNetBinFolderPath = "bin";

        public InstanceFactory(IPathMapper pathMapper = null)
        {
            if(pathMapper == null)
            {
                this.PathMapper = PathMapperFactory.PathMapper;
            }
            else
            {
                this.PathMapper = pathMapper;
            }
        }

        #region IInstanceFactory Members

        public IPathMapper PathMapper { get; set; }

        public T CreateObject<T>(ITypeDescriptor typeDescriptor, params object[] arguments)
        {
            if(typeDescriptor.Type == null)
            {
                typeDescriptor.Type = this.GetTypeFromTypeDescriptor(typeDescriptor);
            }

            object[] args;

            if(arguments != null && arguments.Length > 0)
            {
                args = arguments;
            }
            else
            {
                args = typeDescriptor.Arguments;
            }

            object obj = Activator.CreateInstance(typeDescriptor.Type, args);
            return (T)obj;
        }

        public Type GetTypeFromTypeDescriptor(ITypeDescriptor typeDescriptor)
        {
            GetValidAssemblyPath(ref typeDescriptor);

            Assembly assembly = Assembly.LoadFrom(typeDescriptor.AssemblyPath);

            if (assembly == null)
            {
                throw new ArgumentException("Cannot load assembly from the path: " + typeDescriptor.AssemblyPath);
            }

            Type type = assembly.GetType(typeDescriptor.TypeName);

            if (type == null)
            {
                throw new ArgumentException(string.Format("Type \"{0}\" not found in the assembly: {1}", typeDescriptor.TypeName, typeDescriptor.AssemblyPath));
            }

            return type;
        }

        #endregion // IInstanceFactory Members

        private void GetValidAssemblyPath(ref ITypeDescriptor typeDescriptor)
        {
            string assemblyPath;

            if (System.Web.HttpContext.Current != null)
            {
                // ASP.NET application
                if (!Path.IsPathRooted(typeDescriptor.AssemblyPath))
                {
                    // map path to the application folder for Web applications
                    string binFolder = System.Web.HttpContext.Current.Server.MapPath(aspNetBinFolderPath);
                    assemblyPath = binFolder + "\\" + typeDescriptor.AssemblyPath;

                    if(TryExtensions(ref assemblyPath))
                    {
                        typeDescriptor.AssemblyPath = assemblyPath;
                        return;
                    }
                }
            }

            assemblyPath = this.PathMapper.MapAssemblyBasePath(typeDescriptor.AssemblyPath);

            if(TryExtensions(ref assemblyPath))
            {
                typeDescriptor.AssemblyPath = assemblyPath;
            }
            else
            {
                throw new ArgumentException("Assembly not found: " + typeDescriptor.AssemblyPath);
            }
        }

        private bool TryExtensions(ref string assemblyPath)
        {
            if(File.Exists(assemblyPath))
            {
                return true;
            }
            if (File.Exists(assemblyPath + dllExtension))
            {
                assemblyPath = assemblyPath + dllExtension;
                return true;
            }
            else if (File.Exists(assemblyPath + exeExtension))
            {
                assemblyPath = assemblyPath + exeExtension;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}